package com.wzl.customviewproject.util;

import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * 作者：longxiansen
 * 功能：一个简单的流式布局
 * 目标：
 * 1、实现最基本的一个流式布局展示，做到多个标签超出一行则显示到下一行
 * 2、动态添加数据
 * 3、实现标签的隐藏、展开。
 * 4、实现标签的点击事件
 * 5、实现标签的多样式展示，比如带icon的标签、字体颜色字体大小不同的标签。
 */
public class FlowLayout extends ViewGroup {
    //直接new对象的时候会调用这个构造方法
    public FlowLayout(Context context) {
        super(context);
    }

    //xml中通过反射调用这个两个参数的构造方法
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //代码中设置了不同的主题（比如白天、黑夜）的时候会调用这个构造方法
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private static int mHorizontalSpacing = dp2px(16);//每个item横向间距
    private static int mVerticalSpacing = dp2px(8);//每个item纵向间距    // step21

    protected static int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());   // step 22
    }


    private void initMeasureParams() { //step19
        allLines = new ArrayList<>();
        lineHeights = new ArrayList<>();
    }

    private List<List<View>> allLines;//记录所有的行,一行一行的存储,用于layout
    List<Integer> lineHeights;//记录每一行的行高，用于layout。    //step13

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        initMeasureParams();   // step20       构造函数里的初始化只会执行一次，但是onMeasure和onLayout会执行多次。

        //测量子view
//        measure();//调用这个方法，需要传参MeasureSpec对象。 涉及到一个知识点MeasureSpec如何生成，它与LayoutParams有什么关系
//        View类中存在一个makeMeasureSpec方法，返回一个int值,由size + mode组成。一个int值有32位，mode占据高2位，size占据剩下的30位。这么处理节省内存。

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();//step3

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);//ViewGroup解析的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);//ViewGroup解析的高度   //step8


        List<View> lineViews = new ArrayList<>();//保存一行中的所有的view
        int lineWidthUsed = 0;//记录这一行已经使用了多宽的size
        int lineHeight = 0;//一行的行高


        int parentNeededWidth = 0;//在measure过程中，子view要求的父ViewGroup的宽度
        int parentNeededHeight = 0;//在measure过程中，子view要求的父ViewGroup的高度     //step10

        int childCount = getChildCount();
        //遍历子view
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams childLP = childView.getLayoutParams();//获取子view的布局参数

//            getChildMeasureSpec() 三个参数：1、父布局的width\height的MeasureSpec，即onMeasure方法的参数。2、父布局的内边距。3、子view的宽高
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);//step4
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//step1


            //获取子view的宽高
            int childMeasuredWidth = childView.getMeasuredWidth();
            int childMeasuredHeight = childView.getMeasuredHeight();//step5

//            https://www.bilibili.com/video/BV1JT4y1L7Wc?p=5&spm_id_from=pageDriver
            //通过宽度来判断是否需要换行，通过换行后的每行的行高来获取整个viewGroup的行高
            //如果需要换行
            if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) { // step7
                allLines.add(lineViews);
                lineHeights.add(lineHeight);    //step14


                //一旦换行，我们就可以判断当前行需要的宽和高了，所以此时要记录下来
                //parentNeededWidth是所有行的最大值
                // 和parentNeededHeight是所有的行的总和
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
                parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;     //step11


                //换行，对这一行数据归零
                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;     //step9
            }

            //view是分行layout的，所以需要记录每一行有哪些view，这样可以方便layout布局。
            lineViews.add(childView);
            //每行都会有自己的宽高
            lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
            lineHeight = Math.max(lineHeight, childMeasuredHeight);//比较已有的行高与新加入的子view的高度，取更高   //step6


            if (i == childCount - 1){
                allLines.add(lineViews);
                lineHeights.add(lineHeight);    //step22        .这最后一步如果不加，那么不足一行的就不会显示，也就是会永远少一行，这是视频代码里没考虑到的。
            }
        }


        //根据子view的度量结果，来重新度量自己viewGroup
        // 作为一个viewGroup,它自己也是一个view,它的大小也需要根据它的父亲给它提供的宽高来度量
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth; //如果父view传进来的是确定大小，那么不能超过这个大小。
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;     //    step12


        //测量自己
//        setMeasuredDimension();需要传入两个参数：宽高。流式布局宽取最宽的一行的宽度，高度取所有高度的和。
        setMeasuredDimension(realWidth, realHeight);//step2

//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //left是相对于父view的左边距,top是相当于父view的上边距，right是当前view右边距离父view左边的距离；bottom是当前view下边距离父view上边的距离。

        //和onMeasure不同，onLayout不需要度量自己，所以只需要度量子view即可。

        int curL = getPaddingLeft();
        int curT = getPaddingTop(); //step15

        int lineCount = allLines.size();//获取layout的行数
        for (int i = 0; i < lineCount; i++) {
            //一行行的布局
            List<View> lineViews = allLines.get(i);
            Integer lineHeight = lineHeights.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);


                int left = curL;
                int top = curT;

                /**
                 *  getMeasuredWidth和getWidth的区别
                 *  getMeasuredWidth :    在measure()过程结束后就可以获取到对应的值；通过setMeasuredDimension()方法来进行设置的。
                 *  getWidth : 在layout()过程结束后才能获取到；通过视图右边的坐标减去左边的坐标计算出来的。
                 */
                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();   //step16  ,将left,top,right,bottom的值传给view.layout方法


                view.layout(left, top, right, bottom);      //step14


                //布局完了一个子节点后加上间距距离,重置左边距值。
                curL = right + mHorizontalSpacing;  //step17
            }


            //一行结束之后，重置左边距离和上边距离
            curL = getPaddingLeft();
            curT = curT + lineHeight + mVerticalSpacing;  // step18

        }
    }
}
